/* buffer.c, Ait Emacs, Kevin Bloom, Public Domain, 2023 */

#include <assert.h>
#include <string.h>
#include "header.h"
#include "termbox.h"

/* list of keywords _must_ end with NULL */
keywords_t keywords[] = {
  {".c", "////", "/*", "*/", FALSE, FALSE},
  {".h", "////", "/*", "*/", FALSE, FALSE},
  {".js", "////", "/*", "*/", TRUE, TRUE},
  {".jsx", "////", "/*", "*/", TRUE, TRUE},
  {".ts", "////", "/*", "*/", TRUE, TRUE},
  {".tsx", "////", "/*", "*/", TRUE, TRUE},
  {".lisp", ";;", NULL, NULL, FALSE, FALSE},
  {".clj", ";;", NULL, NULL, FALSE, FALSE},
  {".cljs", ";;", NULL, NULL, FALSE, FALSE},
  {".hs", "--", NULL, NULL, FALSE, FALSE},
  {".sh", "#", NULL, NULL, FALSE, FALSE},
  {"NULL", NULL, NULL, NULL, FALSE, FALSE },
};

void buffer_init(buffer_t *bp)
{
  bp->b_mark = NOMARK;
  bp->b_pmark = NOMARK;
  bp->b_point = 0;
  bp->b_cpoint = 0;
  bp->b_page = 0;
  bp->b_epage = 0;
  bp->b_reframe = 0;
  bp->b_size = 0;
  bp->b_psize = 0;
  bp->b_flags = 0;
  bp->b_cnt = 0;
  bp->b_buf = NULL;
  bp->b_ebuf = NULL;
  bp->b_gap = NULL;
  bp->b_egap = NULL;
  bp->b_next = NULL;
  bp->b_fname[0] = '\0';
  bp->b_fmtime = 0;
  bp->b_bname[0] = '\0';
  bp->b_path = TRUE;
  bp->b_undo = NULL;
  bp->b_eundo = NULL;
  bp->b_redo = NULL;
  bp->b_eredo = NULL;
  bp->b_keywords = NULL;
}

/* Find a buffer by filename or create if requested */
buffer_t* find_buffer (char *fname, int cflag)
{
  buffer_t *bp = NULL;
  buffer_t *ebp = NULL;
  buffer_t *sb = NULL;
  keywords_t *k;
  char filepath[NAME_MAX];
  int len, extlen, c = 0;

  strcpy(filepath, fname);

  bp = bheadp;
  lastbp = curbp;
  while (bp != NULL) {
    if (strcmp (fname, bp->b_fname) == 0 || strcmp(fname, bp->b_bname) == 0) {
      return (bp);
    }
    bp = bp->b_next;
  }

  if (cflag != FALSE) {
    if ((bp = (buffer_t *) malloc (sizeof (buffer_t))) == NULL)
      return (0);

    buffer_init(bp);
    assert(bp != NULL);

    if(filepath[0] != '\0') {
      strcpy(bp->b_fname, fname);
      modify_buffer_name(bp, 0);
      bp->b_fname[0] = '\0';

      for (c = 0, ebp = bheadp; ebp != NULL; ebp = ebp->b_next, c++) {
        int match = FALSE;
        for(int i = 0; i < strlen(ebp->b_bname); i++)
          if(ebp->b_bname[i] == bp->b_bname[i]) {
            match = TRUE;
          } else {
            match = FALSE;
            break;
          }
        if(match) {
          strcpy(bp->b_fname, fname);
          modify_buffer_name(bp, 1);
          bp->b_fname[0] = '\0';
          modify_buffer_name(ebp, 1);
          break;
        }
      }
      bp->b_bname[strlen(bp->b_bname)] = '\0';
      for(k = keywords; k->slc != NULL; ++k) {
        len = strlen(bp->b_bname);
        extlen = strlen(k->extension) - 1;
        c = 0;
        for(int f = len - 1 - extlen; c <= extlen; f++, c++) {
          if(bp->b_bname[f] != k->extension[c]) {
            c = 0;
            break;
          }
        }
        if(c > 0) {
          bp->b_keywords = k;
          break;
        }
      }
    }

    /* find the place in the list to insert this buffer */
    if (bheadp == NULL) {
      bheadp = bp;
    } else if (strcmp (bheadp->b_fname, fname) > 0) {
      /* insert at the beginning */
      bp->b_next = bheadp;
      bheadp = bp;
    } else {
      for (sb = bheadp; sb->b_next != NULL; sb = sb->b_next)
        if (strcmp (sb->b_next->b_fname, fname) > 0)
          break;

      /* and insert it */
      bp->b_next = sb->b_next;
      sb->b_next = bp;
    }
  }
  return bp;
}

/* unlink from the list of buffers, free associated memory, assumes buffer has been saved if modified */
int delete_buffer (buffer_t *bp)
{
  buffer_t *sb = NULL;

  /* we must have switched to a different buffer first */
  assert(bp != curbp);

  /* if buffer is the head buffer */
  if (bp == bheadp) {
    bheadp = bp->b_next;
  } else {
    /* find place where the bp buffer is next */
    for (sb = bheadp; sb->b_next != bp && sb->b_next != NULL; sb = sb->b_next)
      ;
    assert(sb->b_next == bp || sb->b_next == NULL);
    sb->b_next = bp->b_next;
  }

  if(bp == lastbp) {
    lastbp = NULL;
  }

  /* now we can delete */
  free(bp->b_buf);
  free(bp);
  return TRUE;
}

void last_buffer()
{
  buffer_t *t;
  disassociate_b(curwp);
  if(lastbp == NULL) {
    lastbp = curbp;
    curbp = curbp->b_next != NULL ? curbp->b_next : bheadp;
  } else {
    t = curbp;
    curbp = lastbp;
    lastbp = t;
  }
  associate_b2w(curbp,curwp);
}

void next_buffer()
{
  disassociate_b(curwp);
  lastbp = curbp;
  curbp = curbp->b_next != NULL ? curbp->b_next : bheadp;
  associate_b2w(curbp,curwp);
}

void switch_buffer()
{
  buffer_t *next;
  int ret = 0;
  char message[TEMPBUF] = "Switch to buffer (default ";

  assert(curbp != NULL);
  assert(bheadp != NULL);

  next = lastbp != NULL ? lastbp : curbp->b_next;
  if(next == NULL)
    next = bheadp;
  strcat(message, next->b_bname);
  strcat(message, "): ");

  next = getbuffername(message, (char*)temp, NAME_MAX, &ret);

  if(!ret)
    return;

  if(next == curbp) {
    msg("Same buffer!");
    return;
  }
  if(next == NULL) {
      next = lastbp != NULL ? lastbp : curbp->b_next;
      if(next == NULL)
        next = bheadp;
  }
  if(next != NULL) {
    tb_present();
    disassociate_b(curwp);
    lastbp = curbp;
    curbp = next;
    associate_b2w(curbp,curwp);
  } else {
    msg("Buffer doesn't exist");
  }
}

char* get_buffer_name(buffer_t *bp)
{
  return (strlen(bp->b_fname) > 0) ? bp->b_fname : bp->b_bname;
}

int count_buffers()
{
  buffer_t* bp;
  int i = 0;

  for (i=0, bp=bheadp; bp != NULL; bp = bp->b_next)
    i++;

  return i;
}

int modified_buffers()
{
  buffer_t* bp;

  for (bp=bheadp; bp != NULL; bp = bp->b_next)
    if (bp->b_flags & B_MODIFIED)
      return TRUE;

  return FALSE;
}

/* Used to either truncate or expand buffer name
   flag = 0, truncate to file name only (.mailrc)
   flag = 1, truncate to previous directory / file name (i.e. home/.mailrc)
*/
void modify_buffer_name(buffer_t *bp, int flag)
{
  char *dir, fname[NAME_MAX + 1];
  const char *list_dirs[20];
  strcpy(fname, bp->b_fname);
  int d = 0;
  dir = "\0";
  dir = strtok(fname, "/");

  while( dir != NULL ) {
    list_dirs[d] = dir;
    d++;
    dir = strtok(NULL, "/");
  }
  if(flag) {
    strcpy(bp->b_bname, list_dirs[d-2]);
    strcat(bp->b_bname, "/");
  }
  strcat(bp->b_bname, list_dirs[d-1]);
  free(dir);
}
